// Copyright (c) Microsoft Corporation. All rights reserved.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using System.Diagnostics;
using System.Data.SqlClient;
using System.Data;
using Microsoft.TeamFoundation.VersionControl.Client;
using System.Transactions;
using Microsoft.TeamFoundation.Migration.Toolkit.VC;

namespace Microsoft.TeamFoundation.Migration.Toolkit.VC
{
    /// <summary>
    /// Types that implement this interface are able to serialize IMigrationItems to and 
    /// from a string representation (must be valid XML).
    /// </summary>
    public interface IMigrationItemSerializer
    {
        IMigrationItem LoadItem(string itemBlob, ChangeGroupingMananger manager);
        string SerializeItem(IMigrationItem item);
    }

    // these values are persisted in SQL so do not change them between releases
    public enum ChangeStatus
    {
        Analysis   = 0,
        Pending    = 1,
        InProgress = 2,
        Complete   = 3,
        Skipped    = 4
    }

    // these values are persisted in a database - do not change them between releases
    [Flags]
    public enum MigrationMergeOptions
    {
        None = 0,
        ResolveAcceptYours = 1,
        ResolveAcceptTheirs = 2,
        ResolveAutoMerge = 4,
        Baseless = 8
    }

    // these values are persisted in a database - do not change them between releases
    public enum MigrationDirection
    {
        Unknown = 0,
        SourceToTfs = 1,
        TfsToSource = 2
    }

    /// <summary>
    /// The base class for all change groupings.  The derived class will be responsible for
    /// all persistence operations associated with the change grouping.
    /// </summary>
    public abstract class ChangeGrouping
    {
        /// <summary>
        /// Creates a change grouping using the associated grouping manager.
        /// </summary>
        /// <param name="manager">The change grouping manager of this change group</param>
        protected ChangeGrouping(ChangeGroupingMananger manager)
        {
            m_manager = manager;

            if (m_manager != null)
            {
                m_direction = m_manager.Direction;
                if(m_manager.Session != null)
                {
                    m_sessionId = m_manager.Session.Id;
                }
                
            }
        }

        /// <summary>
        /// Persists the change grouping to the data store.
        /// </summary>
        /// <param name="trx">The transaction context in which the change grouping is saved.</param>
        protected abstract void Create(IMigrationTransaction trx);

        /// <summary>
        /// Updates the existing change grouping in the data store.
        /// </summary>
        /// <param name="trx">The transaction context in which the change grouping is updated.</param>
        protected abstract void Update(IMigrationTransaction trx);

        /// <summary>
        /// Creates and returns an object that implements the IMigrationAction interface.
        /// </summary>
        /// <returns>The newly created IMigrationAction instance</returns>
        public abstract IMigrationAction CreateAction();

        /// <summary>
        /// The name of the change group.  This value is persisted in the version control ConversionHistory table
        /// as the source or target of the conversion operation.  For systems that support numbered changesets,
        /// such as TFS, this should be the changeset ID of the change being migrated.  Other systems will need to
        /// choose an appropriate value.
        /// </summary>
        public virtual string Name
        {
            get
            {
                return m_groupName;
            }
            set
            {
                DemandUnlocked(MigrationToolkitVCResources.GroupingError_Name);
                m_groupName = value;
            }
        }

        /// <summary>
        /// A unique change group ID set to an appropriate value after being persisted to the data store.
        /// </summary>
        public virtual int ChangeGroupId
        {
            get
            {
                return m_changeGroupId;
            }
            protected set
            {
                m_changeGroupId = value;
            }
        }

        /// <summary>
        /// The order in which the change grouping should be executed (relative to other change groupings).
        /// Lower numbers are executed before higher values.
        /// </summary>
        public virtual long ExecutionOrder
        {
            get
            {
                return m_executionOrder;
            }
            set
            {
                DemandUnlocked(MigrationToolkitVCResources.GroupingError_Order);
                m_executionOrder = value;
            }
        }

        /// <summary>
        /// The migration direction of this change (Source to TFS, or TFS to Source)
        /// </summary>
        public virtual MigrationDirection Direction
        {
            get
            {
                return m_direction;
            }
            set
            {
                DemandUnlocked(MigrationToolkitVCResources.GroupingError_Direction);
                m_direction = value;
            }
        }

        /// <summary>
        /// The change grouping manager associated with this change group.
        /// </summary>
        public ChangeGroupingMananger Manager
        {
            get
            {
                return m_manager;
            }
        }

        /// <summary>
        /// The change owner (or author)
        /// </summary>
        public virtual string Owner
        {
            get
            {
                if (!m_ownerSet && m_owner == null)
                {
                    setDefaultOwner();
                }

                return m_owner;
            }
            set
            {
                DemandUnlocked(MigrationToolkitVCResources.GroupingError_Owner);                    
                m_owner = value;
                m_ownerSet = true;
            }
        }

        /// <summary>
        /// The change comment
        /// </summary>
        public virtual string Comment
        {
            get
            {
                return m_comment;
            }
            set
            {
                DemandUnlocked(MigrationToolkitVCResources.GroupingError_Comment);
                m_comment = (value == null) ? string.Empty : value;
            }
        }

        /// <summary>
        /// The ID of the migration session in which the change is being migrated.
        /// </summary>
        public virtual string SessionId
        {
            get
            {
                return m_sessionId;
            }
            set
            {
                DemandUnlocked(MigrationToolkitVCResources.GroupingError_SessionId);
                m_sessionId = (value == null) ? string.Empty : value;
            }
        }

        /// <summary>
        /// The UTC time that the change was submitted to the source system
        /// </summary>
        public virtual DateTime ChangeTimeUtc
        {
            get
            {
                return m_changeTime;
            }
            set
            {
                DemandUnlocked(MigrationToolkitVCResources.GroupingError_Time);
                m_changeTime = value;
            }
        }

        /// <summary>
        /// The current status of the change grouping.
        /// </summary>
        public virtual ChangeStatus Status
        {
            get
            {
                return m_status;
            }
            set
            {
                // status can be changed when locked.
                switch (value)
                {
                    case ChangeStatus.Analysis:
                    case ChangeStatus.Pending:
                    case ChangeStatus.InProgress:
                    case ChangeStatus.Complete:
                    case ChangeStatus.Skipped:
                        m_status = value;
                        break;
                    default:
                        throw new MigrationException(
                            string.Format(MigrationToolkitVCResources.Culture, 
                            MigrationToolkitVCResources.UnknownChangeStatusValue, 
                            value));
                }
            }
        }

        /// <summary>
        /// The actions that are a part of this change group.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
        public virtual Collection<IMigrationAction> Actions
        {
            get
            {
                return m_actions;
            }
            protected set
            {
                m_actions = new Collection<IMigrationAction>(value);
            }
        }

        /// <summary>
        /// Adds a new action to the change grouping.
        /// </summary>
        /// <param name="action">The action to add.</param>
        public virtual void AddAction(IMigrationAction action)
        {
            if (action == null)
            {
                throw new ArgumentNullException("action");
            }

            DemandUnlocked(MigrationToolkitVCResources.GroupingError_Add);

            if (action.Order < 0)
            {
                action.Order = m_actions.Count;
            }

            m_actions.Add(action);
        }

        /// <summary>
        /// Saves the change grouping to the backing store.  By default this uses a transactional
        /// context returned by DataAccessManager.Current.StartTransaction().
        /// </summary>
        public virtual void Save()
        {
            Debug.Assert(this.SessionId != null);
            Debug.Assert(this.Direction != MigrationDirection.Unknown); 
            Debug.Assert(this.Manager != null);

            using (IMigrationTransaction trx = DataAccessManager.Current.StartTransaction())
            {
                Save(trx);
                trx.Complete();
            }
        }

        /// <summary>
        /// Saves the change grouping to the backing store using the provided transactional context.
        /// </summary>
        /// <param name="trx">The transaction context to save the change grouping in.</param>
        public virtual void Save(IMigrationTransaction trx)
        {
            Debug.Assert(Direction != MigrationDirection.Unknown);

            if (m_changeGroupId == int.MinValue)
            {
                Create(trx);
            }
            else
            {
                Update(trx);
            }
        }

        /// <summary>
        /// If the change grouping is currently locked this method should throw an exception.  Returning
        /// indicates that the change grouping is unlocked.
        /// </summary>
        /// <param name="msg"></param>
        protected virtual void DemandUnlocked(string msg)
        {
            if (m_locked)
            {
                throw new MigrationException(msg);
            }
        }

        /// <summary>
        /// If true the change grouping is locked, unlocked otherwise.
        /// </summary>
        protected virtual bool Locked
        {
            get
            {
                return m_locked;
            }
            set
            {
                m_locked = value;
            }
        }

        private void setDefaultOwner()
        {
            Debug.Assert(!m_ownerSet);

            if (!m_manager.Session.TryGetValue<string>(
                "DefaultChangeOwner", out m_owner))
            {
                m_owner = null;
            }

            m_ownerSet = true;
        }

        int m_changeGroupId = int.MinValue;
        long m_executionOrder;
        string m_owner;
        string m_comment;
        DateTime m_changeTime;
        ChangeStatus m_status = ChangeStatus.Analysis;
        string m_sessionId = string.Empty;
        string m_groupName = string.Empty;

        MigrationDirection m_direction;

        bool m_locked;

        bool m_ownerSet;

        Collection<IMigrationAction> m_actions = new Collection<IMigrationAction>();

        ChangeGroupingMananger m_manager;
    }
}
